package com.siyoumi.util;

import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.util.*;

@Data
@Slf4j
public class XHttpClient {
    static public String METHOD_POST = "POST";
    static public String METHOD_GET = "GET";
    static public String METHOD_PUT = "PUT";

    private RequestConfig.Builder configBulider;

    static public XHttpClient getInstance() {
        return XHttpClient.getInstance(3000);
    }

    static public XHttpClient getInstance(int connectTimeout) {
        return new XHttpClient(connectTimeout);
    }

    public XHttpClient() {
        this.configBulider = getConfigBulider();
    }

    public XHttpClient(RequestConfig.Builder config) {
        this.configBulider = config;
    }

    public XHttpClient(int connectTimeout) {
        this.configBulider = getConfigBulider(connectTimeout);
    }

    /**
     * 设置代理
     *
     * @param host
     * @param port
     */
    public void setProxy(String host, int port) {
        HttpHost proxy = new HttpHost(host, port);
        this.configBulider.setProxy(proxy);
    }


    /**
     * 毫秒
     *
     * @param connectTimeout
     */
    static public RequestConfig.Builder getConfigBulider(int connectTimeout) {
        return RequestConfig.custom()
                .setConnectTimeout(connectTimeout) //连接超时3秒
                .setConnectionRequestTimeout(connectTimeout) //连接超时3秒
                .setSocketTimeout(connectTimeout);
    }

    static public RequestConfig.Builder getConfigBulider() {
        return getConfigBulider(3000);
    }

    /**
     * get请求
     *
     * @param url     请求地址
     * @param headers 头部信息
     */
    @SneakyThrows
    public String get(String url, Map<String, String> headers) {
        URIBuilder uriBuilder = new URIBuilder(url);
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        //header
        if (headers != null) {
            headers.forEach(httpGet::addHeader);
        }

        return executeReturnStr(httpGet);
    }

    /**
     * json提交
     *
     * @param url      请求地址
     * @param headers  请求头
     * @param postData json参数
     * @return 结果对象
     */
    @SneakyThrows
    public String postJson(String url, Map<String, String> headers, Object postData) {
        if (headers == null) {
            headers = new HashMap<>();
        }
        headers.put("Content-Type", "application/json; charset=UTF-8");
        headers.put("Accept", "application/json");
        CloseableHttpResponse response = postReturnObj(url, headers, postData);
        return EntityUtils.toString(response.getEntity());
    }

    public String putJson(String url, Map<String, String> headers, Object postData) throws IOException {
        if (headers == null) {
            headers = new HashMap<>();
        }

        headers.put("Content-Type", "application/json; charset=UTF-8");
        headers.put("Accept", "application/json");

        CloseableHttpResponse response = methodReturnObj(url, headers, XJson.toJSONString(postData), METHOD_PUT);
        return EntityUtils.toString(response.getEntity());
    }

    @SneakyThrows
    public String postBody(String url, Map<String, String> headers, String raw) {
        if (headers == null) {
            headers = new HashMap<>();
        }
        headers.put("Content-Type", "atext/plain");
        CloseableHttpResponse response = methodReturnObj(url, headers, raw, METHOD_POST);
        return EntityUtils.toString(response.getEntity());
    }

    @SneakyThrows
    public byte[] postJsonReturnByte(String url, Map<String, String> headers, Map<String, Object> postData) {
        CloseableHttpResponse response = postReturnObj(url, headers, postData);
        return EntityUtils.toByteArray(response.getEntity());
    }

    @SneakyThrows
    public CloseableHttpResponse postReturnObj(String url, Map<String, String> headers, Object postData) {
        if (headers == null) {
            headers = new HashMap<>();
        }
        if (headers.containsKey("Content-Type")) {
            headers.put("Content-Type", "application/json; charset=UTF-8");
        }
        if (headers.containsKey("Accept")) {
            headers.put("Accept", "application/json");
        }

        String postStr = "";
        if (postData != null) {
            if (postData instanceof String) {
                postStr = (String) postData;
            } else {
                postStr = XJson.toJSONString(postData);
            }
        }
        return methodReturnObj(url, headers, postStr, METHOD_POST);
    }

    @SneakyThrows
    public CloseableHttpResponse methodReturnObj(String url, Map<String, String> headers, String postStr, String method) {
        URIBuilder uriBuilder = new URIBuilder(url);
        HttpEntityEnclosingRequestBase httpSet;
        switch (method.toUpperCase()) {
            case "PUT":
                httpSet = new HttpPut(uriBuilder.build());
                break;
            default:
                httpSet = new HttpPost(uriBuilder.build());
        }

        //header
        if (headers != null) {
            headers.forEach(httpSet::addHeader);
        }

        httpSet.setEntity(new StringEntity(postStr, "UTF-8"));

        return execute(httpSet);
    }

    /**
     * 表单提交
     *
     * @param url      请求地址
     * @param headers  请求头
     * @param postData json参数
     * @return 结果对象
     */
    @SneakyThrows
    public String postForm(String url, Map<String, String> headers, Map<String, String> postData) {
        URIBuilder uriBuilder = new URIBuilder(url);
        HttpPost httpPost = new HttpPost(uriBuilder.build());
        //header
        httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
        if (headers != null) {
            headers.forEach(httpPost::addHeader);
        }

        List<NameValuePair> formData = new ArrayList<>();
        postData.forEach((k, v) -> {
            formData.add(new BasicNameValuePair(k, v));
        });
        httpPost.setEntity(new UrlEncodedFormEntity(formData, "UTF-8"));

        return executeReturnStr(httpPost);
    }


    @SneakyThrows
    public CloseableHttpResponse execute(HttpRequestBase request) {
        request.setConfig(configBulider.build());

        //忽略SSL证书
        TrustStrategy acceptingTrustStrategy = (x509Certificates, authType) -> true;
        SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
        SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());

        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        httpClientBuilder.setSSLSocketFactory(connectionSocketFactory);

//        BasicHttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager();
//        httpClientBuilder.setConnectionManager(connectionManager);

        CloseableHttpClient httpClient = httpClientBuilder.build();
        CloseableHttpResponse execute = httpClient.execute(request);

        return execute;
    }

    @SneakyThrows
    public String executeReturnStr(HttpRequestBase request) {
        CloseableHttpResponse response = execute(request);
        String returnStr = EntityUtils.toString(response.getEntity());

        log.debug(returnStr);
        return returnStr;
    }
}
